//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// List view control helpers.
//

# include "StdAfx.h"
# include "ListView.h"
# include "Menu.h"

void AddColumn (HWND _hList, int _i, const char * _strText, bool _bAlignRight) {
	LVCOLUMN clm;
	clm.mask       = LVCF_FMT | LVCF_TEXT;
	clm.fmt        = _bAlignRight ? LVCFMT_RIGHT : LVCFMT_LEFT;
	clm.pszText    = const_cast<char *> (_strText);
	clm.cchTextMax = ::strlen (_strText);
	ListView_InsertColumn (_hList, _i, & clm);
}

void AutoSizeColumns (HWND _hList) {
	const size_t cColumns = Header_GetItemCount (ListView_GetHeader (_hList));
	for (size_t c = 0; c < cColumns; ++ c)
		ListView_SetColumnWidth (_hList, c, LVSCW_AUTOSIZE_USEHEADER);
}

void SetImageListForColumns (HWND _hList, HIMAGELIST _hil) {
	const HWND hHdr = ListView_GetHeader (_hList);
	Header_SetImageList (hHdr, _hil);
}

void SetImageForColumn (HWND _hList, size_t _cCol, size_t _cImage) {
	LVCOLUMN lvc;
	::memset (& lvc, 0, sizeof (lvc));
	lvc.mask   = LVCF_FMT;
	ListView_GetColumn (_hList, _cCol, & lvc);

	lvc.mask   = LVCF_FMT | LVCF_IMAGE;
	lvc.fmt   |= LVCFMT_COL_HAS_IMAGES | LVCFMT_IMAGE;
	lvc.iImage = _cImage;
	ListView_SetColumn (_hList, _cCol, & lvc);
}

void RemoveImageForColumn (HWND _hList, size_t _cCol) {
	LVCOLUMN lvc;
	::memset (& lvc, 0, sizeof (lvc));
	lvc.mask   = LVCF_FMT;
	lvc.fmt   &= ~LVCFMT_COL_HAS_IMAGES;
	ListView_SetColumn (_hList, _cCol, & lvc);
}

LPARAM GetLParam (HWND _hList, size_t _cItem) {
	LVITEM lvi;
	lvi.mask     = LVIF_PARAM;
	lvi.iItem    = _cItem;
	lvi.iSubItem = 0;

	ListView_GetItem (_hList, & lvi);

	return lvi.lParam;
}

int GetImage (HWND _hList, size_t _cItem) {
	LVITEM lvi;
	lvi.mask     = LVIF_IMAGE;
	lvi.iItem    = _cItem;
	lvi.iSubItem = 0;

	ListView_GetItem (_hList, & lvi);

	return lvi.iImage;
}

void SetSelection (HWND _hList, size_t _cItem) {
	if (_cItem == -1) {
		const int nSel = ListView_GetSelectionMark (_hList);
		if (nSel == -1)
			return;
		ListView_SetItemState (_hList, _cItem, 0, LVIS_SELECTED | LVIS_FOCUSED);
	} else {
		ListView_SetItemState (_hList, _cItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
		ListView_SetSelectionMark (_hList, _cItem);
	}
}

void UpdateSelectionMark (HWND _hList) {
	const size_t cItems = ListView_GetItemCount (_hList);
	for (size_t c = 0; c < cItems; ++ c)
		if (ListView_GetItemState (_hList, c, LVIS_SELECTED) & LVIS_SELECTED) {
			ListView_SetSelectionMark (_hList, c);
			return;
		}
}

static
int _GetSelection (HWND _hList) {
	// NOTE: ListView_GetSelectionMark() is not good because item sorting does not update sel mark.
	const size_t cItems = ListView_GetItemCount (_hList);
	for (size_t c = 0; c < cItems; ++ c)
		if (ListView_GetItemState (_hList, c, LVIS_SELECTED) & LVIS_SELECTED)
			return c;

	return -1;
}

void RemoveSelection (HWND _hList) {
	const int iSelItem = ListView_GetSelectionMark (_hList);
	if (iSelItem != -1)
		ListView_SetItemState (_hList, iSelItem, 0, LVIS_SELECTED | LVIS_FOCUSED);
}

void EnsureSelectionVisible (HWND _hList) {
	const int iSelItem = _GetSelection/*ListView_GetSelectionMark*/ (_hList);
	if (iSelItem != -1)
		ListView_EnsureVisible (_hList, iSelItem, FALSE);
}

void __cdecl InsertItem (HWND _hList, size_t _cItem, LPARAM _lParam, const char * _strFormat, ...) {
	va_list vl;
	va_start (vl, _strFormat);

	char strBuf [256];
	::vsprintf (strBuf, _strFormat, vl);

	LVITEM lvi;
	lvi.mask       = LVIF_TEXT | LVIF_PARAM;
	lvi.iItem      = _cItem;
	lvi.iSubItem   = 0;
	lvi.pszText    = strBuf;
	lvi.cchTextMax = sizeof (strBuf)/sizeof (strBuf [0]);
	lvi.lParam     = _lParam;

	ListView_InsertItem (_hList, & lvi);

	va_end (vl);
}

void __cdecl SetItemText (HWND _hList, size_t _cItem, const char * _strFormat, ...) {
	va_list vl;
	va_start (vl, _strFormat);

	char strBuf [256];
	::vsprintf (strBuf, _strFormat, vl);

	LVITEM lvi;
	lvi.mask       = LVIF_TEXT;
	lvi.iItem      = _cItem;
	lvi.iSubItem   = 0;
	lvi.pszText    = strBuf;
	lvi.cchTextMax = sizeof (strBuf)/sizeof (strBuf [0]);

	ListView_SetItem (_hList, & lvi);

	va_end (vl);
}

void SetItemIcon (HWND _hList, size_t _cItem, size_t _cIconIndex) {
	LVITEM lvi;
	lvi.mask     = LVIF_IMAGE;
	lvi.iItem    = _cItem;
	lvi.iSubItem = 0;
	lvi.iImage   = _cIconIndex;

	ListView_SetItem (_hList, & lvi);
}

void __cdecl SetSubItemText (HWND _hList, size_t _cItem, size_t _cSubItem, const char * _strFormat, ...) {
	va_list vl;
	va_start (vl, _strFormat);

	char strBuf [256];
	::vsprintf (strBuf, _strFormat, vl);

	ListView_SetItemText (_hList, _cItem, _cSubItem, strBuf);

	va_end (vl);
}

void SelectColumn (HWND _hList, size_t _cColumn) {
	const size_t cRecords = ListView_GetItemCount (_hList);

	LVITEM lvi;
	lvi.mask      = LVIF_STATE;
	lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;

	for (size_t cColumn = 0; true; ++ cColumn) {
		LVCOLUMN lvc = {LVCF_SUBITEM};
		if (! ListView_GetColumn (_hList, cColumn, & lvc))
			break;

		lvi.iSubItem = cColumn;
		lvi.state    = cColumn == _cColumn ? LVIS_SELECTED | LVIS_FOCUSED : 0;
		for (size_t cRecord = 0; cRecord < cRecords; ++ cRecord) {
			lvi.iItem = cRecord;
			ListView_SetItem (_hList, & lvi);
		}
	}

	// Ensure the column is visible.
	if (_cColumn != -1) {
		RECT rect;
		::GetClientRect (_hList, & rect);

		RECT rectSub;
		ListView_GetSubItemRect (_hList, 0, _cColumn, LVIR_BOUNDS, & rectSub);

		if (rectSub.left < 0)
			ListView_Scroll (_hList, rectSub.left, 0);
		else if (rectSub.right > rect.right) {
			ListView_Scroll (_hList, rectSub.right - rect.right, 0);

			ListView_GetSubItemRect (_hList, 0, _cColumn, LVIR_BOUNDS, & rectSub);
			if (rectSub.left < 0)
				ListView_Scroll (_hList, rectSub.left, 0);
		}
	}
}

/////////////////////////////////////////////////////////////

size_t GetLVColumns (HWND _hList) {
	const HWND hHdr = ListView_GetHeader (_hList);
	return Header_GetItemCount (hHdr);
}

# include "ParseHelpers.h"
# include "Settings.h"

static
void _LoadLVHeaders (HWND _hList, const TCHAR * _strSettingName, const int * _pDefaultOrder = NULL) {
	TCHAR strData [256] = "";
	GetSetting (_strSettingName, strData, sizeof (strData)/sizeof (strData [0]), NULL);

	if (strData [0] == '\0') {
		if (_pDefaultOrder != NULL) {
			const size_t cColumns = GetLVColumns (_hList);
			ListView_SetColumnOrderArray (_hList, cColumns, _pDefaultOrder);
		}
		return;
	}

	const TCHAR * p = strData;
	const TCHAR * const pEnd = strData + sizeof (strData)/sizeof (strData [0]);
	unsigned long ulCount = 0;
	if (! ParseDecU (p, pEnd, & p, ulCount))
		return;

	int * const pOrder = (int *) ::alloca (sizeof (size_t)*ulCount);
	size_t c;
	for (c = 0; c < ulCount; ++ c) {
		OmitSpaces (& p, pEnd);

		unsigned long ulOrder = 0;
		if (ParseDecU (p, pEnd, & p, ulOrder))
			pOrder [c] = ulOrder;
		else
			pOrder [c] = c;
	}
	ListView_SetColumnOrderArray (_hList, ulCount, pOrder);

	for (c = 0; c < ulCount; ++ c) {
		OmitSpaces (& p, pEnd);

		unsigned long ulWidth = 0;
		if (! ParseDecU (p, pEnd, & p, ulWidth))
			return;
		ListView_SetColumnWidth (_hList, c, ulWidth);
	}
}

static
void _SaveLVHeaders (HWND _hList, const TCHAR * _strSettingName) {
	const size_t cColumns = GetLVColumns (_hList);

	TCHAR strData [256];
	TCHAR * p = strData;
	const TCHAR * const pEnd = strData + sizeof (strData)/sizeof (strData [0]);

	p += ::_stprintf (p, "%d ", cColumns);

	int * const pOrder = (int *) ::alloca (sizeof (size_t)*cColumns);
	ListView_GetColumnOrderArray (_hList, cColumns, pOrder);

	size_t n;
	for (n = 0; n < cColumns; ++ n) {
		assert (p < pEnd);
		if (p >= pEnd)
			break;

		p += ::_stprintf (p, "%d ", pOrder [n]);
	}

	for (n = 0; n < cColumns; ++ n) {
		assert (p < pEnd);
		if (p >= pEnd)
			break;

		const int nWidth = ListView_GetColumnWidth (_hList, n);
		p += ::_stprintf (p, "%d ", nWidth);
	}

	SaveSetting (_strSettingName, strData);
}

//////////////////////////////////////////////////////////////////////

CListView::CListView () :
	m_hList (NULL),
	m_hilSortingArrows (NULL),
	m_nSortingColumn (-1),
	m_bSortingAscending (true),
	m_pfnCompare (NULL),
	m_lParamSort (0)
{}

void CListView::Init (HWND _hWndParent, WORD _wID, const char * _strSetting, int _nDefaultSortColumn) {
	assert (m_hList == NULL);
	m_hList = ::GetDlgItem (_hWndParent, _wID);

	m_strSetting = _strSetting;
	if (_strSetting) {
		TCHAR * const strSortColSetting   = (char *) alloca (sizeof (char)*(m_strSetting.size () + 20));
		::strcpy (strSortColSetting, _strSetting);
		::strcat (strSortColSetting, "_SortCol");
		m_nSortingColumn = GetSetting (strSortColSetting, (DWORD) _nDefaultSortColumn);

		TCHAR * const strSortOrderSetting = (char *) alloca (sizeof (char)*(m_strSetting.size () + 20));
		::strcpy (strSortOrderSetting, _strSetting);
		::strcat (strSortOrderSetting, "_SortOrder");
		m_bSortingAscending = GetSetting (strSortOrderSetting, true);
	}
}

void CListView::Destroy () {
	if (m_hilSortingArrows) {
		ImageList_Destroy (m_hilSortingArrows);
		m_hilSortingArrows = NULL;
	}
}

void CListView::SetExtStyle (DWORD _dwExtStyle) {
	ListView_SetExtendedListViewStyleEx (m_hList, _dwExtStyle, _dwExtStyle);
}

void CListView::SetImageList (HIMAGELIST _hil, bool _bShared) {
	if (_bShared)
		::SetWindowLong (m_hList, GWL_STYLE, ::GetWindowLong (m_hList, GWL_STYLE) | LVS_SHAREIMAGELISTS);
	ListView_SetImageList (m_hList, _hil, LVSIL_SMALL);
}

void CListView::LoadHeaders () {
	if (m_strSetting.empty ())
		return;

	TCHAR * const strHeadersSetting = (char *) alloca (sizeof (char)*(m_strSetting.size () + 20));
	::strcpy (strHeadersSetting, m_strSetting.c_str ());
	::strcat (strHeadersSetting, "_Headers");

	_LoadLVHeaders (m_hList, strHeadersSetting);
}

void CListView::SaveHeaders () {
	if (m_strSetting.empty ())
		return;

	TCHAR * const strHeadersSetting = (char *) alloca (sizeof (char)*(m_strSetting.size () + 20));
	::strcpy (strHeadersSetting, m_strSetting.c_str ());
	::strcat (strHeadersSetting, "_Headers");

	_SaveLVHeaders (m_hList, strHeadersSetting);
}

void CListView::SelectByLParam (LPARAM _lParam) {
	const size_t cItems = ListView_GetItemCount (m_hList);
	for (size_t c = 0; c < cItems; ++ c) {
		if (GetLParam (m_hList, c) == _lParam) {
			SetSelection           (m_hList, c);
			ListView_EnsureVisible (m_hList, c, FALSE);
			return;
		}
	}
}


# include "Globals.h"
# include "Resource.h"

void CListView::Sort () {
	if (m_hilSortingArrows == NULL) {
		m_hilSortingArrows = LoadImageList (IDB_SortingArrows, 8, c_colLtGray);
		SetImageListForColumns (m_hList, m_hilSortingArrows);
		if (m_nSortingColumn != -1)
			SetImageForColumn  (m_hList, m_nSortingColumn, m_bSortingAscending ? 0 : 1);
	}

	if (m_nSortingColumn == -1)
		return;

	ListView_SortItemsEx (m_hList, _CompareItems, reinterpret_cast<LPARAM> (this));
}

int CALLBACK CListView::_CompareItems (LPARAM _lItem1, LPARAM _lItem2, LPARAM _lData) {
	const CListView * const pThis = reinterpret_cast<CListView *> (_lData);
	const HWND hTypes = pThis->m_hList;

	if (pThis->m_pfnCompare == NULL) {
		char str1 [64], str2 [64];
		ListView_GetItemText (hTypes, _lItem1, pThis->m_nSortingColumn, str1, 64); 
		ListView_GetItemText (hTypes, _lItem2, pThis->m_nSortingColumn, str2, 64);

		// Compare as strings.
		return pThis->m_bSortingAscending ? ::lstrcmp (str1, str2) : ::lstrcmp (str2, str1);
	} else {
		const int nRet = pThis->m_pfnCompare (_lItem1, _lItem2, pThis->m_lParamSort);
		return pThis->m_bSortingAscending ? nRet : -nRet;
	}
}

void CListView::Sort_OnColumnClick (const NMHDR * _pInfo) {
	const NMLISTVIEW * const pInfo2 = reinterpret_cast<const NMLISTVIEW *> (_pInfo);
	assert (_pInfo->hwndFrom == m_hList);

	if (m_nSortingColumn != pInfo2->iSubItem) {
		RemoveImageForColumn (m_hList, m_nSortingColumn);
		m_nSortingColumn = pInfo2->iSubItem;
	} else
		m_bSortingAscending = ! m_bSortingAscending;
	SetImageForColumn (m_hList, m_nSortingColumn, m_bSortingAscending ? 0 : 1);

	// Save sorting order.
	if (! m_strSetting.empty ()) {
		TCHAR * const strSortColSetting   = (char *) alloca (sizeof (char)*(m_strSetting.size () + 20));
		::strcpy (strSortColSetting, m_strSetting.c_str ());
		::strcat (strSortColSetting, "_SortCol");
		SaveSetting (strSortColSetting, (DWORD) m_nSortingColumn);

		TCHAR * const strSortOrderSetting = (char *) alloca (sizeof (char)*(m_strSetting.size () + 20));
		::strcpy (strSortOrderSetting, m_strSetting.c_str ());
		::strcat (strSortOrderSetting, "_SortOrder");
		SaveSetting (strSortOrderSetting, m_bSortingAscending);
	}

	Sort ();
}

void CListView::PopupMenu_OnHeaderRClick (const NMHDR * _pInfo) {
	const HWND hHdr = ListView_GetHeader (m_hList);
	if (hHdr != _pInfo->hwndFrom)
		return;

	const DWORD dwPos = ::GetMessagePos ();
	POINT pt = {LOWORD (dwPos), HIWORD (dwPos)};

	CPopupMenu pm;

	const size_t c_cCmd0 = 100;

	const size_t cColumns = Header_GetItemCount (hHdr);
	for (size_t c = 0; c < cColumns; ++ c) {
		TCHAR strText [32];

		LVCOLUMN lvc;
		::memset (& lvc, 0, sizeof (lvc));
		lvc.mask       = LVCF_WIDTH | LVCF_TEXT;
		lvc.pszText    = strText;
		lvc.cchTextMax = sizeof (strText)/sizeof (strText [0]);
		ListView_GetColumn (m_hList, c, & lvc);

		::AppendMenu (pm, MF_STRING | MF_ENABLED | (lvc.cx > 0 ? MF_CHECKED : MF_UNCHECKED), c_cCmd0 + c, strText);
	}

	const BOOL bCmd = ::TrackPopupMenuEx (
		pm,
		TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY,
		pt.x, pt.y,
		hHdr, NULL
	);
	if (bCmd != 0) {// ! Cancel
		const size_t cCol = bCmd - c_cCmd0;

		const int nWidth = ListView_GetColumnWidth (m_hList, cCol);
		ListView_SetColumnWidth (m_hList, cCol, nWidth > 0 ? 0 : LVSCW_AUTOSIZE_USEHEADER);
	}
}
